致远OA A8-V5 任意文件读取漏洞分析
漏洞环境
致远A8 V7.0
漏洞利用
读取./../../base/conf/datasourceCtp.properties路径下的数据库配置文件
1 | POST /seeyon/officeservlet HTTP/1.1 |
![]() |
|---|
读取出数据库密码/1.0/VWZ0dTIzNC8=,经过加密,再github上下载解密脚本,解密就可以拿到明文密码。
https://github.com/Rvn0xsy/PassDecode-jar
![]() |
|---|
漏洞分析
exp中的请求路径为/seeyon/officeservlet
在web.xml中搜索 officeservlet
![]() |
|---|
找到接口映射到对应的类,跟入 com.seeyon.ctp.common.office.OfficeServlet文件
1 | public class OfficeServlet extends HttpServlet { |
在这个继承了HttpServlet类中,主要功能都实现在doGet中。
代码当中有很多看不懂的地方很正常,我们只需要重点关注漏洞利用点,和恶意数据到达利用点的路径就可以了。
从前端拿到参数的关键 在33行
![]() |
|---|
1 | handWriteManager.readVariant(request, msgObj); |
这条语句大致作用就是从给handWriteManager类中一些属性进行赋值,跟进这个方法中
![]() |
|---|
调用了msgObj.ReadPackage(request)去解析request中的数据,看看是怎么解析的,继续跟进
![]() |
|---|
在这个方法中,先是判断http请求体的长度,再将http请求体的内容读取到this.FStream属性中,然后判断this.FError是否为空串,再初始化这个类时,会将this.FError赋值为空串。
![]() |
|---|
所以这里一定会调用this.StreamToMsg()方法,跟进该方法中。
1 | private boolean StreamToMsg() { |
上面这段代码中就像是在http请求体中圈地一样,先是指定了http请求体前64为来存储this.FVersion、this.FMsgText的长度、this.FError的长度、this.FMsgFile的长度。后面则是根据指定的长度在http请求体64位之后切割并赋值给对应的属性,文件读取所获取的路径只需要通过这里this.FMsgText里拿,我们只需要赋值给this.FMsgText。其余则都属性的长度都赋值为0,这样可跳过对对应属性进行赋值。
完成而后又回到readVariant方法中
![]() |
|---|
在后续的赋值操作中,通过传递对应字符串调用msgObj.GetMsgByName 方法从this.FMsgText属性中获取值。这里再获取值的时候也是做了一些操作的,我们跟入msgObj.GetMsgByName方法中。
1 | public String GetMsgByName(String var1) { |
在这段代码中,其实就是在this.FMsgText获取值,在this.FMsgText这个属性中通过关键字=的方式去获取关键字=xxx中的xxx,并且对xxx进行了DecodeBase64解密,这个解码并不是常见的base64解码方式,而且变种过的base64编码,也就是说当我们传递参数值的时候,还需要对xxx进行EncodeBase64加密。
这篇文章中详细讲了致远 OA 变种 BASE64 算法的加解密方法
文章中给出了一个加解密的脚本
1 | var a = "gx74KW1roM9qwzPFVOBLSlYaeyncdNbI=JfUCQRHtj2+Z05vshXi3GAEuT/m8Dpk6"; |
这个脚本a2b是解密,得到是正常的base64编码值,b2a是加密,得到的是加密后的值。c保存的是需要被加密或者解密的值,d则是最后转换的结果。
所以在后续构造文件读取路径的时候,需要通过该加密脚本对路径进行加密。
经过前面的分析,我们已经知道了request请求是如何被解析了以及在获取值的时候做了解密的操作操作,接下来回到OfficeServlet类中
![]() |
|---|
这里获取OPTION的值,而后会根据得到的值调用对应的方法。
关键点 在56行
![]() |
|---|
这里调用了handWriteManager.taoHong方法,跟入
![]() |
|---|
也是获取了TEMPLATE、COMMAND、affairMemberId、affairMemberName的值,随后传入了officePath调用了msgObj.MsgFileLoad(officePath),继续跟入
1 | public boolean MsgFileLoad(String var1) { |
这个方法大致功能传入一个文件路径,然后文件的值读取到this.FMsgFile属性中。
而这也是照成文件读取关键的一个点,根据这里往后推,文件的路径可通过TEMPLATE变量拿到,而TEMPLATE则可以通过关键字TEMPLATE在msgObj.GetMsgByName 方法中拿到,而GetMsgByName这个方法本身是在this.FMsgText这个属性中获取值,而this.FMsgText则是在http请求体中截取的值,而http请求体我们可控。到目前为止,我们可以通过http请求体控制文件路径来读取对应文件,但是还差一步,怎么回显文件内容。
在 OfficeServlet.class 类
![]() |
|---|
在101行调用了handWriteManager.sendPackage(response, msgObj),我们跟入
![]() |
|---|
调用了msgObj.SendPackage(response) 再次跟入
![]() |
|---|
这里会调用this.MsgVariant()将结果写入到http响应中。我们跟入 this.MsgVariant()
![]() |
|---|
这里返回了this.FStream 的值,作为返回到前端的内容。
跟入this.MsgToStream中
![]() |
|---|
通过FMsgText、FError、FFileSize创建了字节数组输出流,然后写入了FMsgText、FError以及最关键也就是文件读取内容赋值的变量FMsgFile,最后都赋值给了FStream,然后在MsgVariant方法中被返回到前端,以上就是整个任意文件读取漏洞的原理分析。















